 package org.mybatis.core.jdbc;
 
 import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.sql.Blob;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.ibatis.type.JdbcType;
import org.mybatis.annotation.Column;
import org.mybatis.annotation.OneToMany;
import org.mybatis.annotation.OneToOne;
import org.mybatis.annotation.PKClass;
import org.mybatis.annotation.Table;
import org.mybatis.core.enums.StatementType;
import org.mybatis.core.interfaces.NULLMapper;
import org.mybatis.core.interfaces.NameGenerator;
import org.mybatis.util.DefaultFieldFilter;
import org.mybatis.util.Ognl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
 
@SuppressWarnings("all")
 public class SQLStatement
 {
   protected static final Logger LOG = LoggerFactory.getLogger(SQLStatement.class);
   protected static final Class<? extends Annotation> MYBATISTABLE = Table.class;
   protected static final Class<? extends Annotation> MYBATISCOLUMN = Column.class;
   protected static final Class<? extends Annotation> ONE = OneToOne.class;
   protected static final Class<? extends Annotation> MANY = OneToMany.class;
   protected static final Class<? extends Annotation> PKCLASS = PKClass.class;
 
   private final String lineSeparator = System.getProperty("line.separator", "\r\n");
   private static final String AND = ") \nAND (";
   private static final String OR = ") \nOR (";
   private static final String F = "`";
   private boolean auto_increment = false;
   private String pkId;
   private Set<String> pkSet = new LinkedHashSet<>();
   private String version;
   private String mulPkFieldPrefix = null;
   private String entityClassName;
   private List<String> notColumns = new ArrayList<>();
   private List<String> notUpdateCols = new ArrayList<>();
   private String[] parentCol = null;
   private NameGenerator nameGenerator;
   private StringBuilder resultMapBuilder = null;
   private Class<?> clazz = null;
   private List<String> tables;
   private List<String> sets = new ArrayList<>();
   private List<String> mergeSets = new ArrayList<>();
   private List<String> columnsSelect = new ArrayList<>();
   private List<String> whereSelect = new ArrayList<>();
   private List<String> whereClause = new ArrayList<>();
   private List<String> where = new ArrayList<>();
   private List<String> columnsInsert = new LinkedList<>();
   private List<String> valuesInsert = new ArrayList<>();
 
   private static Map<Class<?>, JdbcType> CLAZZJDBCMAP = new HashMap<Class<?>, JdbcType>() { private static final long serialVersionUID = 4184820457075021150L; } ;
 
   public SQLStatement(Class<?> clazz, NameGenerator nameGenerator)
   {
     this.clazz = clazz;
     this.nameGenerator = nameGenerator;
     this.resultMapBuilder = new StringBuilder(512);
 
     String tableName = this.nameGenerator.getGeneratorNameByName(clazz.getSimpleName()).toUpperCase();
     String defaultPkId = "id"; 
     String version = null;
     this.entityClassName = clazz.getName();
     if (clazz.isAnnotationPresent(MYBATISTABLE)) {
       Table antable = (Table)clazz.getAnnotation(MYBATISTABLE);
       this.parentCol = antable.parentCol();
       this.notColumns.addAll(Arrays.asList(antable.notColumn()));
       this.notUpdateCols.addAll(Arrays.asList(antable.notUpdateCol()));
       if (!isEmpty(antable.tabName())) {
         tableName = antable.tabName();
       }

       this.auto_increment = antable.autoIncrement();

       this.pkSet.addAll(Arrays.asList(antable.pkId()));
       if (Ognl.isNotEmpty(antable.pkId())) {
         this.pkId = antable.pkId()[0];
       }

       if (this.pkSet.size() > 1) {
         this.auto_increment = false;
       }

       if (!isEmpty(antable.version())) {
         version = antable.version();
       }
     }
     this.tables = Arrays.asList(new String[] { F+tableName+F });
     if (this.pkSet.isEmpty()) {
       this.pkSet.add(defaultPkId);
       this.pkId = defaultPkId;
     }
     this.version = version;
   }

   public void addSets(String setValue){
     this.sets.add(setValue);
   }

   public void addMergeSets(String mergeValue) {
     this.mergeSets.add(mergeValue);
   }
   
   public void addColumnsSelect(String value) {
     this.columnsSelect.add(value);
   }

   public void addWhereSelect(String value) {
     this.whereSelect.add(value);
   }
   
   public void addWhereClause(String value) {
	     this.whereClause.add(value);
   }

   public void addWhere(String value) {
     this.where.add(value);
   }

   public void addColumnsInsert(String value) {
     this.columnsInsert.add(value);
   }

   public void removeColumnsInsert(String value) {
     this.columnsInsert.remove(value);
   }

   public void addValuesInsert(String value) {
     this.valuesInsert.add(value);
   }
 
   public String sql(Appendable a, StatementType statementType)
   {
     SafeAppendable builder = new SafeAppendable(a);
     if (statementType == null)
       return null;
     String answer;
     switch (statementType) {
     case DELETE:
       answer = deleteSQL(builder);
       break;
     case DELETES:
         answer = deletesSQL(builder);
         break;
     case DELETE_MODEL:
         answer = deletesModelSQL(builder);
         break;
     case INSERT:
       answer = insertSQL(builder);
       break;
     case SELECT:
       answer = selectSQL(builder);
       break;
     case UPDATENULL:
        answer = updateNULLSQL(builder);
        break;
     case UPDATE:
        answer = updateSQL(builder);
        answer = formatUpdateSqlForMulPk(answer);
        break;
     case SELECT_COND:
       answer = selectConditionSQL(builder);
       break;
     case SELECT_COUNT:
       answer = selectCountSQL(builder);
       break;
     case MODEL_WHERE_CLAUSE:
         answer = modelwhereClauseSQL(builder);
         break;
       
     default:
       answer = null;
     }
     return answer;
   }
 
   private void sqlClause(SafeAppendable builder, String keyword, List<String> parts, String open, String close, String conjunction)
   {
     if (!parts.isEmpty()) {
       if (!builder.isEmpty())
         builder.append(" ");
       builder.append(keyword);
       builder.append(" ");
       builder.append(open);
       String last = "________";
       int i = 0; for (int n = parts.size(); i < n; i++) {
         String part = (String)parts.get(i);
         if ((i > 0) && (!part.equals(AND)) && (!part.equals(OR)) && (!last.equals(AND)) && (!last.equals(OR)))
         {
           builder.append(conjunction);
         }
         builder.append(part);
         last = part;
       }
       builder.append(close);
     }
   }
 
   private void initFields(){
		if (this.parentCol.length==0) {
	     ReflectionUtils.doWithFields(this.clazz, new ReflectionUtils.FieldCallback(){
	       public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException{
	         SQLStatement.this.initSqlStatement(field);
	       }
	     }
	     , null);
	   }else{
		   Field[] fields = this.clazz.getDeclaredFields();
	       for (Field field : fields) {
	         initSqlStatement(field);
	       }
	       Object targetClass = this.clazz.getSuperclass();
	       if ((targetClass != null) && (targetClass != Object.class)) {
	         ReflectionUtils.doWithFields((Class)targetClass, new ReflectionUtils.FieldCallback(){
	             public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
	               SQLStatement.this.initSqlStatement(field); 
	             }
	         }, new DefaultFieldFilter(this.parentCol));
	       }
	   }
   }
 
   public Map<String, String> getMybatisMapperSqlMap()
   {
     initFields();
     if (this.whereSelect.size() == 0) {
       throw new RuntimeException("entity = {" + this.entityClassName + "} does not exist Primary key,Please check ?????????????????");
     }
	 Map replaceMap = new HashMap();
     replaceMap.put("pkId", getPkId());
     replaceMap.put("version", getVersion());
 
     replaceMap.put("insertSql", sql(new StringBuilder(512), StatementType.INSERT));
     replaceMap.put("updateNULLSql", sql(new StringBuilder(512), StatementType.UPDATENULL));
     replaceMap.put("updateSql", sql(new StringBuilder(512), StatementType.UPDATE));
     replaceMap.put("resultField", getResultMap());
     replaceMap.put("deleteSql", sql(new StringBuilder(64), StatementType.DELETE));
     replaceMap.put("deletesSql", sql(new StringBuilder(64), StatementType.DELETES));
     replaceMap.put("deleteModelSql", sql(new StringBuilder(64), StatementType.DELETE_MODEL));
     replaceMap.put("querySql", sql(new StringBuilder(256), StatementType.SELECT));
 
     if (this.auto_increment)
       replaceMap.put("autoIncrementFix", " useGeneratedKeys=\"true\" keyProperty=\"" + this.pkId + "\" ");
     else {
       replaceMap.put("autoIncrementFix", "");
     }
 
     replaceMap.put("queryConditionSql", sql(new StringBuilder(256), StatementType.SELECT_COND));
     replaceMap.put("queryCountSql", sql(new StringBuilder(256), StatementType.SELECT_COUNT));
     replaceMap.put("whereClause", sql(new StringBuilder(256), StatementType.MODEL_WHERE_CLAUSE));
 
     return replaceMap;
   }
 
   private void initMulPKField(Field field)
   {
     PKClass mulPk = (PKClass)field.getAnnotation(PKCLASS);
     this.mulPkFieldPrefix = (field.getName() + ".");
     boolean hasColumn = false;
     hasColumn = Ognl.isNotEmpty(mulPk.column());
 
     if ((hasColumn) && (mulPk.column().length != this.pkSet.size())) {
       throw new RuntimeException("entity = {" + this.entityClassName + "}  Primary key and PKClass are inconsistent ,Please check ?????????????????");
     }
     int pkidx = 0;
     String pkitem = null; String pkColumnName = null;
     for (Iterator it = this.pkSet.iterator(); it.hasNext(); ) {
       pkitem = (String)it.next();
       if (hasColumn)
         pkColumnName = mulPk.column()[pkidx];
       else {
         pkColumnName = this.nameGenerator.getGeneratorNameByName(pkitem);
       }
       String columnName = F+pkColumnName+F;
       addColumnsSelect(columnName);
       addColumnsInsert(columnName);
       addWhere(columnName + "=#{" + pkitem + "}");
       addWhereSelect(columnName + "=#{" + pkitem + "}");
       addValuesInsert("#{" + this.mulPkFieldPrefix + pkitem + "}");
       this.resultMapBuilder.append("<id property=\"").append(this.mulPkFieldPrefix + pkitem).append("\" column=\"" + pkColumnName + "\" />").append(this.lineSeparator);
 
       pkidx++;
     }
   }
 
   private void initLazyField(Field field)
   {
     if (field.isAnnotationPresent(ONE)) {
       OneToOne anOne = (OneToOne)field.getAnnotation(ONE);
       this.resultMapBuilder.append("<association property=\"" + field.getName()).append("\" column=\"" + anOne.column() + "\" select=\"");
 
       String namespace = anOne.mapperBy() == NULLMapper.class ? anOne.mapperByNameSpace() : anOne.mapperBy().getName();
       if (!isEmpty(namespace)) {
         this.resultMapBuilder.append(namespace).append(".");
       }
       this.resultMapBuilder.append(anOne.sqlId() + "\"/>");
       this.notColumns.add(field.getName());
     }
 
     if (field.isAnnotationPresent(MANY)) {
       OneToMany anMany = (OneToMany)field.getAnnotation(MANY);
       this.resultMapBuilder.append("<collection property=\"" + field.getName()).append("\" column=\"" + anMany.column() + "\" select=\"");
 
       String namespace = anMany.mapperBy() == NULLMapper.class ? anMany.mapperByNameSpace() : anMany.mapperBy().getName();
       if (!isEmpty(namespace)) {
         this.resultMapBuilder.append(namespace).append(".");
       }
       this.resultMapBuilder.append(anMany.sqlId() + "\"/>");
       this.notColumns.add(field.getName());
     }
   }
 
   public void initSqlStatement(Field field)
   {
     if ("serialVersionUID".equals(field.getName())) {
       return;
     }
     if (field.isAnnotationPresent(PKCLASS)) {
       initMulPKField(field);
       return;
     }
     String columnName = this.nameGenerator.getGeneratorNameByName(field.getName());
     initLazyField(field);
     
     if (getNotColumns().contains(field.getName())) {
       return;
     }
     boolean updateFlag = true;
     String typeHandler = null;
     String formula = null;
     if (field.isAnnotationPresent(MYBATISCOLUMN)) {
       Column anColumn = (Column)field.getAnnotation(MYBATISCOLUMN);
       if (!isEmpty(anColumn.name())) {
         columnName = anColumn.name();
       }
       typeHandler = anColumn.typeHandler();
       updateFlag = anColumn.updateable();
       formula = anColumn.formula();
     }
     //带`符号
     String fColumnName = F+columnName+F;
     addColumnsSelect(fColumnName);
     addColumnsInsert(fColumnName);
     String sqlColumnValue = getSqlColumnValue(field, formula);
 
     if (this.pkSet.contains(field.getName())) {
       addWhere(fColumnName + "=#{" + field.getName() + "}");
       addWhereSelect(fColumnName + "=#{" + field.getName() + "}");
       addValuesInsert("#{" + field.getName() + "}");

       this.resultMapBuilder.append("<id property=\"").append(field.getName()).append("\" column=\"" + columnName + "\" />").append(this.lineSeparator);
     }
     else if (field.getName().equals(getVersion())) {
       addWhere(fColumnName + " = " + sqlColumnValue);
       addSets(fColumnName + " = " + fColumnName + "+1");
 
       addMergeSets("<if test=\"@org.mybatis.util.Ognl@isNotEmpty( " + (String)this.pkSet.iterator().next() + " )\">" + this.lineSeparator + fColumnName + " = " + fColumnName + "+1 , " + this.lineSeparator + "</if>");
       addValuesInsert(sqlColumnValue);
 
       this.resultMapBuilder.append("<result property=\"").append(field.getName()).append("\" column=\"" + columnName + "\"/>").append(this.lineSeparator);
     }
     else{
       this.resultMapBuilder.append("<result property=\"").append(field.getName()).append("\" column=\"" + columnName + "\"" + isBlobField(field, typeHandler) + "/>").append(this.lineSeparator);
 
       if ((updateFlag) && (!this.notUpdateCols.contains(field.getName()))) {
         addSets(fColumnName + " = " + sqlColumnValue);
 
         addMergeSets("<if test=\"@org.mybatis.util.Ognl@isNotEmpty( " + field.getName() + " )\">" + this.lineSeparator + fColumnName + " = " + sqlColumnValue + " , " + this.lineSeparator + "</if>");
       }
      
       addValuesInsert(sqlColumnValue);
     }
     addWhereClause("<if test=\"@org.mybatis.util.Ognl@isNotEmpty( " + field.getName() + " )\">" + this.lineSeparator + "AND " + fColumnName + " = #{" + field.getName() + "}" + this.lineSeparator + "</if>");
   }
 
   private String isBlobField(Field field, String typeHandler) {
     if (Blob.class == field.getType())
       return " javaType=\"byte[]\" jdbcType=\"BLOB\" typeHandler=\"org.apache.ibatis.type.BlobTypeHandler\"";
     if (!isEmpty(typeHandler)) {
       return " typeHandler=\"" + typeHandler + "\"";
     }
     return "";
   }
 
   private JdbcType getJdbcType(Field field)
   {
     return CLAZZJDBCMAP.containsKey(field.getType()) ? (JdbcType)CLAZZJDBCMAP.get(field.getType()) : JdbcType.VARCHAR;
   }
 
   public String getResultMap() {
     return this.resultMapBuilder.toString();
   }
 
   private String getSqlColumnValue(Field field, String formula) {
     if (!isEmpty(formula)) {
       return formula;
     }
     String jdbcTypeStr = "#{" + field.getName();
     if (field.isAnnotationPresent(MYBATISCOLUMN)) {
       Column anColumn = (Column)field.getAnnotation(MYBATISCOLUMN);
       if (anColumn.nullable()) {
         jdbcTypeStr = jdbcTypeStr + ",jdbcType=" + anColumn.jdbcType();
       }
       if (JdbcType.BLOB.equals(anColumn.jdbcType())) {
         jdbcTypeStr = jdbcTypeStr + ",javaType=byte[],typeHandler=org.apache.ibatis.type.BlobTypeHandler";
       }
       if ((!isEmpty(anColumn.typeHandler())) && (jdbcTypeStr.indexOf("typeHandler=") == -1))
         jdbcTypeStr = jdbcTypeStr + ",typeHandler=" + anColumn.typeHandler();
     }
     else {
       JdbcType jdbcType = getJdbcType(field);
       jdbcTypeStr = jdbcTypeStr + ",jdbcType=" + jdbcType;
       if (JdbcType.BLOB.equals(jdbcType)) {
         jdbcTypeStr = jdbcTypeStr + ",javaType=byte[],typeHandler=org.apache.ibatis.type.BlobTypeHandler";
       }
     }
     jdbcTypeStr = jdbcTypeStr + "}";
     return jdbcTypeStr;
   }
 
   private String selectSQL(SafeAppendable builder)
   {
     sqlClause(builder, "SELECT", this.columnsSelect, "", "", ", ");
     sqlClause(builder, "FROM", this.tables, "", "", ", ");
     sqlClause(builder, "WHERE", this.whereSelect, "", "", " AND ");
     return builder.toString();
   }
 
   private String formatUpdateSqlForMulPk(String sql)
   {
     if (isEmpty(this.mulPkFieldPrefix)) {
       return sql;
     }
     return StringUtils.replace(sql, "=#{", "=#{" + this.mulPkFieldPrefix);
   }
 
   private String selectConditionSQL(SafeAppendable builder)
   {
     sqlClause(builder, "SELECT", this.columnsSelect, "", "", ", ");
     sqlClause(builder, "FROM", this.tables, "", "", ", ");
     return builder.toString();
   }
 
   private String selectCountSQL(SafeAppendable builder)
   {
     sqlClause(builder, "SELECT", Arrays.asList(new String[] { "COUNT(1)" }), "", "", ", ");
     sqlClause(builder, "FROM", this.tables, "", "", ", ");
     return builder.toString();
   }
 
   private String insertSQL(SafeAppendable builder)
   {
     sqlClause(builder, "INSERT INTO", this.tables, "", "", "");
     sqlClause(builder, "", this.columnsInsert, "(", ")", ", ");
     sqlClause(builder, this.lineSeparator + "VALUES", this.valuesInsert, "(", ")", ", ");
     return builder.toString();
   }
 
   private String deleteSQL(SafeAppendable builder) {
     sqlClause(builder, "DELETE FROM", this.tables, "", "", "");
 
     sqlClause(builder, "WHERE", this.whereSelect, "", "", " AND ");
     return builder.toString();
   }
   
   private String deletesSQL(SafeAppendable builder) {
	     sqlClause(builder, "DELETE FROM", this.tables, "", "", "");
	     List<String> inList = new  ArrayList<>();
	     String pkColumnName =  F+this.nameGenerator.getGeneratorNameByName(this.pkId)+F;
	     inList.add(pkColumnName + " IN ("+this.lineSeparator+"<foreach collection=\"paramPKs\" item=\"item\" index=\"index\" separator=\",\" >"+this.lineSeparator+
        "#{item}"+this.lineSeparator+
        "</foreach> " +this.lineSeparator+")");
	     
	     sqlClause(builder, "WHERE", inList,  "", "", ", ");
	     return builder.toString();
	   }
   private String deletesModelSQL(SafeAppendable builder) {
	     sqlClause(builder, "DELETE FROM", this.tables, "", "", "");
	     return builder.toString();
	   }
 
   public String updateNULLSQL(SafeAppendable builder) {
     sqlClause(builder, "UPDATE", this.tables, "", "", "");
     sqlClause(builder, "SET", this.sets, "", "", ", ");
     sqlClause(builder, "WHERE", this.where, "", "", " AND ");
     return builder.toString();
   }
 
   private String updateSQL(SafeAppendable builder)
   {
     sqlClause(builder, "UPDATE", this.tables, "", "", this.lineSeparator);
     sqlClause(builder, " ", this.mergeSets, "<trim prefix=\"SET\" suffixOverrides=\",\">" + this.lineSeparator, "</trim>" + this.lineSeparator, this.lineSeparator);
     sqlClause(builder, "WHERE", this.where, "", "", " AND ");
     return builder.toString();
   }
   
   private String modelwhereClauseSQL(SafeAppendable builder){
	   sqlClause(builder, " ", this.whereClause, "<where>" + this.lineSeparator,  this.lineSeparator + "</where>",  this.lineSeparator);
	   return builder.toString();
   }
 
   public boolean isAuto_increment() {
     return this.auto_increment;
   }
 
   public void setAuto_increment(boolean auto_increment) {
     this.auto_increment = auto_increment;
   }
 
   public String getPkId() {
     return this.pkId;
   }
 
   public void setPkId(String pkId) {
     this.pkId = pkId;
   }
 
   public String getVersion() {
     return this.version;
   }
 
   public void setVersion(String version) {
     this.version = version;
   }
 
   public List<String> getNotColumns()
   {
     return this.notColumns;
   }
 
   public void setNotColumns(List<String> notColumns) {
     this.notColumns = notColumns;
   }
 
   public static boolean isEmpty(String value)
   {
     return (value == null) || (value.equals(""));
   }
 }

